﻿using System.IO;
using System.Collections.Generic;
using System;
using System.Text.RegularExpressions;

namespace ANLP.Parser
{
    public class ParserParser : Parser<ParserExpression>
    {
        TextWriter writer;
        public ParserParser(ParserLexer lexer, TextWriter writer)
            : base(lexer)
        {
            this.writer = writer;
            SetParameter("Class", "Parser");
            SetParameter("Lexer", "Lexer");
            SetParameter("Namespace", "ANLP");
            SetParameter("Visibility", "internal");
            SetParameter("Code", "");
            SetParameter("ExpressionType", "object");
        }

        private void SetParameter(string name, string value)
        {
            SetParameter(new ParameterExpression(name) { Value = value });
        }

        static ParserParser()
        {
            states = new int[][] { 
                new int[] { 0 },                                                                        //0
                new int[] { (int)Tokens.Token, (int)Tokens.Colon, -1, -6, (int)Tokens.SemiColon},       //1 
                new int[] { (int)Tokens.Pipe, -1, -6 },                                                 //2
                new int[] { (int)Tokens.Token, -1 },                                                    //3
                new int[] { (int)Tokens.SinglePercent, (int)Tokens.Token, -3, -5 },                     //4
                new int[] { (int)Tokens.DoublePercent, (int)Tokens.Code,(int)Tokens.DoublePercent},     //5
                new int[] { (int)Tokens.MultipartToken },                                               //6
                new int[] { (int)Tokens.Token },                                                        //7
                new int[] { (int)Tokens.Code },                                                         //8
                new int[] { },                                                                          //9
                new int[]{ (int)Tokens.Comma, -3 },                                                     //10
            };
            rules = new int[][] {
                //% or %% or rule
                new int[]{ -1, -4, -5 },
                //Code or nothing
                new int[]{ -8,-9 },
                //self, expression or alternative
                new int[]{ 2, -3, -2 },

                //token or Multipart Token
                new int[]{ -6,-7 },
                //Comma and again
                new int[]{ 4, -10 },
                //Comma and again or nothing
                new int[]{ 4, -9 },

                //expression or empty
                new int[]{ 2,-9 },
            };


        }

        private static int[][] states;

        private static int[][] rules;


        protected override int[][] States
        {
            get { return states; }
        }

        protected override int[][] Rules
        {
            get { return rules; }
        }

        private RuleExpression currentRule;
        private ParserExpression currentExpression;

        private Dictionary<string, RuleExpression> ruleExpressions = new Dictionary<string, RuleExpression>();

        protected override void DoAction(int state, int step, ParserExpression<ParserExpression>[] expressions)
        {
            switch (state)
            {
                //EOF
                case -1:
                    GenerateClass();
                    break;
                case 1:
                    switch (step)
                    {
                        case 0:
                            currentExpression = currentRule = new RuleExpression() { Name = lexer.GetText() };
                            break;
                        case 1:
                            currentRule.States.Add((StateExpression)(currentExpression = new StateExpression()));
                            break;
                        case int.MaxValue:
                            ruleExpressions.Add(currentRule.Name, currentRule);
                            break;
                    }
                    break;
                case 2:
                    switch (step)
                    {
                        case 0:
                            currentExpression = new StateExpression();
                            break;
                        case int.MaxValue:
                            currentRule.States.Add((StateExpression)currentExpression);
                            break;
                        default:
                            break;
                    }
                    break;
                case 3:
                    switch (step)
                    {
                        case 0:
                            ((StateExpression)currentExpression).Add(new TokenExpression(lexer.GetText()));
                            break;
                    }
                    break;
                case 4:
                    switch (step)
                    {
                        case 1:
                            currentExpression = new ParameterExpression(lexer.GetText());
                            break;
                        case int.MaxValue:
                            SetParameter((ParameterExpression)currentExpression);
                            break;
                    }
                    break;
                case 5:
                    switch (step)
                    {
                        case 0:
                            lexer.ChangeState(1);
                            break;
                        case 1:
                            SetParameter("Code", lexer.GetText());
                            break;
                        case int.MaxValue:
                            lexer.ChangeState(0);
                            break;
                        default:
                            break;
                    }
                    break;
                case 6:
                case 7:
                    if (step == int.MaxValue)
                        if (((ParameterExpression)currentExpression).Value == null || !((ParameterExpression)currentExpression).Value.EndsWith(lexer.GetText()))
                            ((ParameterExpression)currentExpression).Value += lexer.GetText();
                    break;
                case 10:
                    switch (step)
                    {
                        case 1:
                            if (((ParameterExpression)currentExpression).Value == null || !((ParameterExpression)currentExpression).Value.EndsWith(lexer.GetText()))
                                ((ParameterExpression)currentExpression).Value += lexer.GetText();
                            break;
                        case 0:
                            ((ParameterExpression)currentExpression).Value += ",";
                            break;
                    }
                    break;
                case 8:
                    //if (step == -1)
                    //    lexer.ChangeState(2);

                    if (step == int.MaxValue)
                    {
                        ((StateExpression)currentExpression).Add(new CodeExpression(lexer.GetText()));
                        lexer.ChangeState(0);
                    }
                    break;
            }
        }

        string[] parsedTexts = new string[20];

        private void SetParameter(ParameterExpression parameterExpression)
        {
            ParameterExpression result;
            if ((parameterExpression.Name == "token" || parameterExpression.Name == "using") && parameters.TryGetValue(parameterExpression.Name, out result))
            {
                result.Value += "," + parameterExpression.Value;
            }
            else
                parameters[parameterExpression.Name] = parameterExpression;
        }

        private void GenerateClass()
        {
            List<string> tokens = new List<string>(parameters["token"].Value.Split(' ', ','));
            for (int i = tokens.Count - 1; i > 0; i--)
            {
                if (tokens.IndexOf(tokens[i]) != i)
                    tokens.RemoveAt(i);
            }

            writer.WriteLine("using System.Collections.Generic;");

            if (parameters.ContainsKey("using"))
            {

                foreach (string @using in parameters["using"].Value.Split(','))
                {
                    writer.Write("using ");
                    writer.Write(@using);
                    writer.WriteLine(";");
                }
            }

            writer.Write("namespace ");
            writer.WriteLine(parameters["Namespace"].Value);
            writer.WriteLine('{');
            writer.Write(parameters["Visibility"].Value);
            writer.WriteLine(" enum Tokens : int");
            writer.WriteLine("{");
            writer.WriteLine("Whitespace,");
            foreach (string token in tokens)
            {
                writer.WriteLine(token + ',');
            }
            writer.WriteLine("}");
            writer.WriteLine();

            writer.WriteLine("[System.Diagnostics.DebuggerNonUserCode]");

            writer.Write(parameters["Visibility"].Value);
            writer.Write(" partial class ");
            writer.Write(parameters["Class"].Value);
            writer.Write(" : ANLP.Parser<");
            writer.Write(parameters["ExpressionType"].Value);
            writer.WriteLine(">");
            writer.WriteLine('{');

            writer.Write("public Parser(");
            writer.Write(parameters["Lexer"].Value);
            writer.WriteLine(" lexer):base(lexer) { }");

            writer.WriteLine("static " + parameters["Class"].Value + " (){");
            //Generate States
            writer.WriteLine("states = new int[][]{");
            Dictionary<string, GoToExpression> generatedRules = new Dictionary<string, GoToExpression>();
            Dictionary<string, GoToExpression> generatedStates = new Dictionary<string, GoToExpression>();
            generatedStates.Add("#", new GoToExpression("#", 0) { RuleTargetsIndex = { new GoToExpression("#", 0, GoToType.Rule) } });
            List<StateExpression> states = new List<StateExpression>();
            Queue<string> nextRuleNames = new Queue<string>();
            foreach (var ruleExpression in ruleExpressions)
                generatedRules.Add(ruleExpression.Key, new GoToExpression(ruleExpression.Key, generatedRules.Count, GoToType.Rule));

            foreach (var ruleExpression in ruleExpressions.Values)
            {
                var rule = generatedRules[ruleExpression.Name];
                for (int i = 0; i < ruleExpression.States.Count; i++)
                {
                    var state = ruleExpression.States[i];
                    GoToExpression gt = new GoToExpression(ruleExpression.Name + i, generatedStates.Count, GoToType.State);
                    rule.RuleTargetsIndex.Add(gt);
                    generatedStates.Add(gt.StateName, gt);

                    bool hasToken = false;
                    int step = -1;
                    foreach (var stateFragment in state.Expressions)
                    {
                        if (stateFragment is CodeExpression)
                        {
                            hasToken = true;
                            gt.AddAction(step, (CodeExpression)stateFragment);
                            continue;
                        }
                        step++;
                        string token = ((TokenExpression)stateFragment).Token;
                        if (tokens.Contains(token))
                        {
                            hasToken = true;
                            //Terminal
                            gt.RuleTargetsIndex.Add(new GoToExpression(token, GoToType.Token));
                        }
                        else
                        {
                            //Non Terminal
                            //if (!gt.RuleTargetsIndex.Contains(generatedRules[token]))
                            gt.RuleTargetsIndex.Add(generatedRules[token]);
                        }
                    }
                    if (!hasToken && state.Expressions.Count == 1)
                    {
                        generatedStates.Remove(gt.StateName);
                        var initialRule = generatedRules[ruleExpression.Name];
                        //foreach is used to get the first element
                        foreach (var stateFragment in state.Expressions)
                        {
                            string token = ((TokenExpression)stateFragment).Token;
                            gt.ChangeNature(generatedRules[token]);
                        }
                    }
                }
                if (rule.RuleTargetsIndex.Count == 2)
                {
                    if (rule.RuleTargetsIndex[1].RuleTargetsIndex.Count == 1 &&
                        rule.RuleTargetsIndex[0].RuleTargetsIndex.Count == 2 &&
                        rule.RuleTargetsIndex[0].RuleTargetsIndex[0] == rule &&
                        rule.RuleTargetsIndex[0].RuleTargetsIndex[1].RuleIndex == rule.RuleTargetsIndex[1].RuleIndex)
                    {
                        generatedStates.Remove(rule.RuleTargetsIndex[0].StateName);
                        generatedStates.Remove(rule.RuleTargetsIndex[1].StateName);
                        rule.RuleTargetsIndex[0].ChangeNature(rule);
                    }
                }
            }

            foreach (var rule in generatedStates.Values)
            {
                writer.Write("\t\tnew int[]{ ");
                foreach (var item in rule.RuleTargetsIndex)
                {
                    switch (item.Type)
                    {
                        case GoToType.State:
                            throw new NotSupportedException();
                        case GoToType.Rule:
                            writer.Write("-" + item.RuleIndex);
                            break;
                        case GoToType.Token:
                            writer.Write("(int)Tokens." + item.StateName);
                            break;
                        default:
                            break;
                    }
                    writer.Write(", ");
                }
                writer.WriteLine("},");
            }
            writer.WriteLine("};");

            writer.WriteLine("rules=new int[][]{");

            foreach (var rule in generatedRules.Values)
            {
                writer.Write("new int[]{ ");
                foreach (var item in rule.RuleTargetsIndex)
                {
                    switch (item.Type)
                    {
                        case GoToType.State:
                            writer.Write("-" + item.RuleIndex);
                            break;
                        case GoToType.Rule:
                            writer.Write(item.RuleIndex);
                            break;
                        case GoToType.Token:
                            throw new NotSupportedException();
                        default:
                            break;
                    }
                    writer.Write(", ");
                }
                writer.WriteLine("},");
            }
            writer.WriteLine("};");

            //writer.WriteLine("};");

            //End of Initialize
            writer.WriteLine("}");

            writer.WriteLine("static int[][] states, rules;");

            //writer.WriteLine("private Dictionary<string, RuleExpression> ruleExpressions = new Dictionary<string, RuleExpression>();");

            writer.Write(@"protected override int[][] States
        {
            get { return states; }
        }

        protected override int[][] Rules
        {
            get { return rules; }
        }

        protected override void DoAction(int state, int step, ParserExpression<");
            writer.Write(parameters["ExpressionType"].Value);

            writer.WriteLine(">[] expressions) {");

            writer.WriteLine("switch(state){");

            writer.WriteLine(@"case 0:
                                    if(step==int.MaxValue)
                                        expressions[expressions.Length-1]=expressions[0];
                                break;");

            Regex dynamicVariables = new Regex(@"\$(\$|\d)");
            foreach (GoToExpression gt in generatedStates.Values)
            {
                if (gt.Actions != null && gt.Actions.Count > 0)
                {
                    writer.Write("case ");
                    writer.Write(gt.RuleIndex);
                    writer.WriteLine(@":{
                switch(step){");
                    foreach (var action in gt.Actions)
                    {
                        writer.Write("case ");
                        writer.Write(action.Key);
                        writer.WriteLine(":{");
                        writer.WriteLine(dynamicVariables.Replace(action.Value.Code, ReplaceVariable));
                        writer.WriteLine("break;}");
                    }
                    writer.WriteLine(@"}
                break;
                }");
                }
            }
            writer.WriteLine("}");

            writer.WriteLine("}");

            writer.Write(parameters["Code"].Value);

            //End of class
            writer.WriteLine('}');
            //End of namespace
            writer.WriteLine('}');

        }

        private string ReplaceVariable(Match m)
        {
            string name = m.Captures[0].Value.Substring(1);
            if (name == "$")
                name = "expressions.Length-1";
            return "expressions[" + name + "]";
        }

        private Dictionary<string, ParameterExpression> parameters = new Dictionary<string, ParameterExpression>();
        private RuleExpression rootRule;
    }
}
